/*--------------------------------------------------------------------------
 *
 * 	You may freely copy, distribute, and reuse the code in this example.
 * 	SHL Systemhouse disclaims any warranty of any kind, expressed or  
 *	implied, as to its fitness for any particular use.
 *
 *
 *	SlidingMatrix
 *
 *	Inherits From:		Matrix
 *
 *	Conforms To:		None.
 *
 *	Declared In:		SlidingMatrix.h
 *
 *
 *------------------------------------------------------------------------*/
#import "SlidingMatrix.h"


static int			rowDropped;
static int			rowDragged;
static BOOL			isDragging;
static NXPoint		currentMouse;


#define BOUND(__a,__low,__high) MIN(MAX((__a),(__low)),(__high))




@implementation SlidingMatrix

/*--------------------------------------------------------------------------
 *	Private Methods
 *------------------------------------------------------------------------*/
- _exchangeRowAt: (int) source with: (int) destination
{
	int	iterator;
	id	sourceCell, destinationCell;
	
	for (iterator = 0; iterator < numCols; iterator++)
		{
		sourceCell = [self cellAt: source : iterator];
		destinationCell = [self cellAt: destination : iterator];
		[self putCell: sourceCell  at: destination : iterator];
		[self putCell: destinationCell  at: source : iterator];
		}
		
	return self;
}


- _reflectScroll
{
	//  If in a ScrollView, reflect scroll if drag is beyond visibleRect...
	
	id			scrollView = [superview superview];
	id			clipView  = superview;
	NXRect		visibleRect, clipViewBoundsRect;
	NXCoord		delta;

	[self getVisibleRect: &visibleRect];
	delta = (currentMouse.y + cellSize.height) - (NX_HEIGHT (&visibleRect) + NX_Y 
		(&visibleRect));

	if (delta > 0.0)
		{
		[clipView getBounds: &clipViewBoundsRect];
		NX_Y (&clipViewBoundsRect) += delta;
		[clipView rawScroll: &clipViewBoundsRect.origin];
		[scrollView reflectScroll: clipView];
		}
	else
		{
		delta = NX_Y (&visibleRect) - currentMouse.y;
		if (delta > 0.0)
			{
			[clipView getBounds: &clipViewBoundsRect];
			NX_Y (&clipViewBoundsRect) -= delta;
			[clipView rawScroll: &clipViewBoundsRect.origin];
			[scrollView reflectScroll: clipView];
			}
		}

	return self;
}


- _reflectSlide
{
	//  Here's where we actually adjust the cells to reflect the slide.  Insert
	//  a new cell at the drop row (this moves any rows > than drop row by
	//  one).  Exchange the dragged cell with the newly inserted cell.  Remove
	//  the original dragged row. If user has dragged beyond last cell, add to
	//  end.
		
	int		rowDraggedOffset = 0;
	NXRect	rect;
	NXPoint	mouseLocation;

	[window getMouseLocation: &mouseLocation];
	[self getBounds: &rect];
	[self convertRect: &rect  toView: nil];
	if (rowDropped == numRows -1  &&  mouseLocation.y < NX_Y (&rect))
		  rowDropped++;

	[window disableFlushWindow];

	[self insertRowAt: rowDropped];
	if (rowDropped < rowDragged)  {  rowDragged++;  rowDraggedOffset = 1;  }
	selectedRow = rowDropped;
	[self _exchangeRowAt: rowDragged  with: rowDropped];
	[self removeRowAt: rowDragged  andFree: YES];

	rowDragged -= rowDraggedOffset;

	[self display];
	[window reenableFlushWindow];
	[window flushWindow];
	return self;
}


- (BOOL) _notifyDelegateWillSlideFromRow: (int) sourceRow
{
	if (delegate  && [delegate respondsTo:
			@selector (slidingMatrix:willSlideFromRow:)])
		return [delegate slidingMatrix: self  willSlideFromRow: sourceRow ];
		
	return YES;
}


- (BOOL) _notifyDelegateWillSlideToRow: (int) destinationRow
{
	if (delegate  &&  [delegate respondsTo:
			@selector (slidingMatrix:willSlideToRow:)])
		return [delegate slidingMatrix: self  willSlideToRow: destinationRow];
		
	return YES;
}


- _notifyDelegateDidSlideFromRow: (int) sourceRow  toRow: (int) destinationRow
{
	if (delegate  &&  [delegate respondsTo:
			@selector (slidingMatrix:didSlideFromRow:toRow:)])
		[delegate slidingMatrix:self didSlideFromRow:sourceRow 
				toRow:destinationRow];
		
	return self;
}


/*--------------------------------------------------------------------------
 *	Initialization and Freeing
 *------------------------------------------------------------------------*/
- initFrame: (const NXRect*) frameRect
{
	id buttonCellPrototype = [[ButtonCell allocFromZone: [self zone]]
			initTextCell:"empty"];
	[buttonCellPrototype setIconPosition:NX_ICONLEFT];
	[buttonCellPrototype setBordered:NO];
	[buttonCellPrototype setIcon:"empty"];
	[buttonCellPrototype setAlignment:NX_LEFTALIGNED];
	[buttonCellPrototype setType:NX_ONOFF];

	[super initFrame:(const NXRect *)frameRect mode:NX_RADIOMODE
		prototype:buttonCellPrototype numRows:0 numCols:0];

	return self;
}


/*--------------------------------------------------------------------------
 *	Accessing the deleagate
 *------------------------------------------------------------------------*/
- delegate
{
	return delegate;
}


- setDelegate: anObject
{
	delegate = anObject;
	return self;
}


/*--------------------------------------------------------------------------
 *	Event Handling
 *------------------------------------------------------------------------*/
- mouseDown: (NXEvent*) theEvent
{
	int 			column, originalEventMask;
	BOOL		scrollViewFlag;
	NXRect 		cellFrame, visibleRect;
	NXCoord 	offset;
	NXEvent* 	nextEvent;

	//  Is user control-dragging...
	if ( ! (theEvent->flags & NX_CONTROLMASK))
		return [super mouseDown: theEvent];

	//  Is there a valid row under the mouse...
	currentMouse = theEvent->location;
	[self convertPoint: &currentMouse fromView: nil];
	[self getRow: &rowDragged  andCol: &column  forPoint: &currentMouse];
	if (rowDragged == -1)  return [super mouseDown: theEvent];
	if ([self _notifyDelegateWillSlideFromRow: rowDragged] == NO)
		return [super mouseDown: theEvent];
	[self selectCellAt: rowDragged : column];

	//  Calculate offset (difference between mouse hit y coord and
	//	cellFrame y coord).  
	[self getCellFrame: &cellFrame at: rowDragged : column];
	offset = currentMouse.y - NX_Y (&cellFrame);

	//  Adjust currentMouse.y to cell frame boundary.
	currentMouse.y -= offset;

	//  Prepare for event loop...
	[window makeFirstResponder: window];
	originalEventMask = [window addToEventMask: NX_MOUSEDRAGGEDMASK];
	scrollViewFlag = ([[superview superview] isKindOf:
			[ScrollView class]] ? YES : NO);
	isDragging = YES;

	while (isDragging)
		{
		nextEvent = [NXApp getNextEvent:NX_MOUSEUPMASK | NX_MOUSEDRAGGEDMASK];
		
		switch (nextEvent->type)
			{
			case NX_MOUSEDRAGGED:
				isDragging = YES;
				currentMouse = nextEvent->location;
				[self convertPoint: &currentMouse  fromView: nil];
				currentMouse.y -= offset;
				currentMouse.y = BOUND (currentMouse.y, NX_Y (&bounds),
					(NX_Y (&bounds) + NX_HEIGHT (&bounds) - cellSize.height));
				if  ([self getVisibleRect: &visibleRect]  &&  scrollViewFlag)
					[self _reflectScroll];
				[self displayFromOpaqueAncestor: &visibleRect  :1  :YES];
				break;

			case NX_MOUSEUP:
				isDragging = NO;
				currentMouse.y += cellSize.height / 2.0;

				if ( ! [self getRow:&rowDropped andCol:&column
						forPoint:&currentMouse])
					{
					currentMouse.y += cellSize.height  / 4.0;
					if ( ! [self getRow:&rowDropped andCol:&column
						forPoint:&currentMouse])
						break;
					}
	
				if ([self _notifyDelegateWillSlideToRow: rowDropped])
					{
					[self _reflectSlide];
					[self selectCellAt: rowDropped : column];
					[self _notifyDelegateDidSlideFromRow:rowDragged 
						toRow:rowDropped];
					}
	
				break;
			}
		}  
	
	[self display];
	[window setEventMask: originalEventMask];
	return self;
}


/*--------------------------------------------------------------------------
 *	Drawing Methods
 *------------------------------------------------------------------------*/
- drawSelf: (const NXRect*)rects : (int) rectCount
{
	int 		iterator;
	NXRect	rect;

	if (! isDragging)  return [super drawSelf: rects  : rectCount];

	[super drawSelf: rects  : rectCount];

	PSgsave ();
	PSsetgray ([window backgroundGray]);
	for (iterator = 0;  iterator < numCols; iterator++) 
		{
		[self getCellFrame: &rect  at: rowDragged : iterator];
		NXRectFill (&rect);
		}
	PSgrestore();


	for (iterator = 0;  iterator < numCols; iterator++) 
		{
		[self getCellFrame: &rect  at: rowDragged  : iterator];
		NX_Y (&rect) = currentMouse.y;
		[[self cellAt: rowDragged : iterator]  drawSelf: &rect  inView: self];
		}
		
	return self;
}


@end
